package com.example.server.message;

import java.lang.reflect.Type;
import java.math.BigInteger;
import java.sql.Time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class MessageFactory {
	private final static Logger log = Log.getLogger(MessageFactory.class);
	private static Gson _exploreGson;

	@SuppressWarnings("unchecked")
	public static Map<String, Object> parseJSONMessage(String json) {
		if (json == null || json.length() == 0) {
			return null;
		}
		Object obj = _exploreGson.fromJson(json, Object.class);
		return (Map<String, Object>) obj;
	}

	@SuppressWarnings("unchecked")
	public static Map<String, Object> parseJSONMessageWithCheck(String json) {
		if (json == null || json.length() == 0) {
			return null;
		}

		try {
			Object obj = _exploreGson.fromJson(json, Object.class);
			return (Map<String, Object>) obj;
		} catch (Exception e) {
			log.debug("is not a valid json string");
		}
		return null;
	}

	public static <T> T parseJSONMessageWithCheck(String json, Class<T> cls) {
		if (json == null || json.length() == 0) {
			return null;
		}

		try {
			T obj = _exploreGson.fromJson(json, cls);
			return obj;
		} catch (Exception e) {
			log.debug("is not a valid json string");
		}
		return null;
	}

	public static <T> T parseJSONMessage(String json, Class<T> cls) {
		if (json == null || json.length() == 0) {
			return null;
		}
		return _exploreGson.fromJson(json, cls);
	}

	public static <T> T parseJSONMessage(String json, Type t) {
		if (json == null || json.length() == 0) {
			return null;
		}
		return _exploreGson.fromJson(json, t);
	}

	public static void init() {
		JsonSerializer<Time> ser = new JsonSerializer<Time>() {

			public JsonElement serialize(Time src, Type typeOfSrc, JsonSerializationContext context) {
				SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
				JsonElement je = new JsonPrimitive(format.format(src));
				return je;
			}

		};

		JsonDeserializer<Time> deser = new JsonDeserializer<Time>() {

			public Time deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
				SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
				Time t = new Time(0);
				try {
					t = new Time(format.parse(json.getAsString()).getTime());
				} catch (ParseException e) {
					e.printStackTrace();
				}
				return t;
			}

		};

		JsonSerializer<BigInteger> bsec = new JsonSerializer<BigInteger>() {

			public JsonElement serialize(BigInteger src, Type typeOfSrc, JsonSerializationContext context) {
				JsonElement je = new JsonPrimitive("" + src.longValue());
				return je;
			}

		};

		JsonSerializer<Double> dsec = new JsonSerializer<Double>() {

			public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
				JsonElement je = new JsonPrimitive("" + src.longValue());
				return je;
			}

		};

		JsonSerializer<Float> floatsec = new JsonSerializer<Float>() {

			public JsonElement serialize(Float src, Type typeOfSrc, JsonSerializationContext context) {
				JsonElement je = new JsonPrimitive("" + src);
				return je;
			}

		};

		JsonSerializer<Integer> intsec = new JsonSerializer<Integer>() {

			public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
				JsonElement je = new JsonPrimitive("" + src);
				return je;
			}

		};

		JsonSerializer<Long> longsec = new JsonSerializer<Long>() {

			public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
				JsonElement je = new JsonPrimitive("" + src);
				return je;
			}

		};
		GsonBuilder gsonB = new GsonBuilder();
		ExclusionStrategy strategy = new ExclusionStrategy() {
			public boolean shouldSkipField(FieldAttributes f) {
				if (f.getName().equals("meta")) {
					return true;
				}
				return false;
			}

			public boolean shouldSkipClass(Class<?> clazz) {
				return false;
			}
		};

		gsonB.addDeserializationExclusionStrategy(strategy);
		gsonB.addSerializationExclusionStrategy(strategy);
		gsonB.registerTypeAdapter(Time.class, ser);
		gsonB.registerTypeAdapter(BigInteger.class, bsec);
		gsonB.registerTypeAdapter(Double.class, dsec);
		gsonB.registerTypeAdapter(Float.class, floatsec);
		gsonB.registerTypeAdapter(Integer.class, intsec);
		gsonB.registerTypeAdapter(Long.class, longsec);
		gsonB.registerTypeAdapter(Time.class, deser);
		gsonB.setDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		gsonB.disableHtmlEscaping();
		_exploreGson = gsonB.create();
	}

	public static String toJson(Object obj) {
		if (obj == null) {
			return null;
		}
		return _exploreGson.toJson(obj);
	}

	/**
	 * 
	 * @param info
	 *            logic info struct
	 * @param out
	 *            struct for msg
	 * 
	 *            only support two level mapping
	 *            info:{profile:{a:b},attr2:[{c:d}]} =>
	 *            info:{profile:{name:b},attr2:[{count:d}]}
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void packInfo(Map<String, Object> info, Map<String, Object> out, Map<String, String> msgMapping) {
		log.debug("packInfo start ...");
		if (out == null || info == null) {
			log.debug("packInfo end info or out is null");
			return;
		}
		if (msgMapping == null) {
			out.putAll(info);
			log.debug("packInfo end msg mapping is null");
			return;
		}

		for (String key : info.keySet()) {
			Object val = info.get(key);
			Object toVal = val;
			if (val instanceof List && ((List) val).size() > 0 && ((List) val).get(0) instanceof Map) {
				toVal = new LinkedList<Map<String, Object>>();
				for (Object obj : (List) val) {
					Map<String, Object> m = new LinkedHashMap<String, Object>();
					for (Object kk : ((Map) obj).keySet()) {
						if (msgMapping.containsKey(kk)) {
							String k = msgMapping.get(kk);
							log.debug("packInfo key " + kk + " => " + k);
							m.put(k, getPackObject(((Map) obj).get(kk), msgMapping));
						} else {
							m.put((String) kk, getPackObject(((Map) obj).get(kk), msgMapping));
						}
					}
					((List) toVal).add(m);
				}
			} else if (val instanceof Map) {
				toVal = new LinkedHashMap<String, Object>();
				for (Object kk : ((Map) val).keySet()) {
					if (msgMapping.containsKey(kk)) {
						String k = msgMapping.get(kk);
						log.debug("packInfo key " + kk + " => " + k);
						((Map) toVal).put(k, getPackObject(((Map) val).get(kk), msgMapping));
					} else {
						((Map) toVal).put((String) kk, getPackObject(((Map) val).get(kk), msgMapping));
					}
				}
			}
			if (msgMapping.containsKey(key)) {
				String k = msgMapping.get(key);
				log.debug("packInfo key " + key + " => " + k);
				out.put(k, toVal);
			} else {
				out.put(key, toVal);
			}
		}
		log.debug("packInfo end ...");
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static Object getPackObject(Object o, Map<String, String> msgMapping) {
		Object val = o;
		Object toVal = val;
		if (val instanceof List && ((List) val).size() > 0 && ((List) val).get(0) instanceof Map) {
			toVal = new LinkedList<Map<String, Object>>();
			for (Object obj : (List) val) {
				Map<String, Object> m = new LinkedHashMap<String, Object>();
				for (Object kk : ((Map) obj).keySet()) {
					if (msgMapping.containsKey(kk)) {
						String k = msgMapping.get(kk);
						log.debug("packInfo key " + kk + " => " + k);
						m.put(k, ((Map) obj).get(kk));
					} else {
						m.put((String) kk, ((Map) obj).get(kk));
					}
				}
				((List) toVal).add(m);
			}
		} else if (val instanceof Map) {
			toVal = new LinkedHashMap<String, Object>();
			for (Object kk : ((Map) val).keySet()) {
				if (msgMapping.containsKey(kk)) {
					String k = msgMapping.get(kk);
					log.debug("packInfo key " + kk + " => " + k);
					((Map) toVal).put(k, ((Map) val).get(kk));
				} else {
					((Map) toVal).put((String) kk, ((Map) val).get(kk));
				}
			}
		}
		return toVal;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void unpackArgs(Map<String, Object> as, Map<String, Object> out, Map<String, String> msgMapping) {
		log.debug("unpackArgs start ...");
		if (out == null || as == null) {
			log.debug("unpackArgs end info or out is null");
			return;
		}
		if (msgMapping == null) {
			out.putAll(as);
			log.debug("unpackArgs end msg mapping is null");
			return;
		}

		for (String key : as.keySet()) {
			Object val = as.get(key);
			Object toVal = val;
			if (val instanceof List && ((List) val).size() > 0 && ((List) val).get(0) instanceof Map) {
				toVal = new LinkedList<Map<String, Object>>();
				for (Object obj : (List) val) {
					Map<String, Object> m = new LinkedHashMap<String, Object>();
					for (Object kk : ((Map) obj).keySet()) {
						String k = mapValueToKey(kk, msgMapping);
						if (k != null) {
							log.debug("unpackArgs key " + kk + " => " + k);
							m.put(k, getUnpackObject(((Map) obj).get(kk), msgMapping));
						} else {
							m.put((String) kk, getUnpackObject(((Map) obj).get(kk), msgMapping));
						}
					}
					((List) toVal).add(m);
				}
			} else if (val instanceof Map) {
				toVal = new LinkedHashMap<String, Object>();
				for (Object kk : ((Map) val).keySet()) {
					String k = mapValueToKey(kk, msgMapping);
					if (k != null) {
						log.debug("unpackArgs key " + kk + " => " + k);
						((Map) toVal).put(k, getUnpackObject(((Map) val).get(kk), msgMapping));
					} else {
						((Map) toVal).put((String) kk, getUnpackObject(((Map) val).get(kk), msgMapping));
					}
				}
			}
			String k = mapValueToKey(key, msgMapping);
			if (k != null) {
				log.debug("unpackArgs key " + key + " => " + k);
				out.put(k, toVal);
			} else {
				out.put(key, toVal);
			}
		}
		log.debug("unpackArgs end ...");
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static Object getUnpackObject(Object o, Map<String, String> msgMapping) {
		Object val = o;
		Object toVal = val;
		if (val instanceof List && ((List) val).size() > 0 && ((List) val).get(0) instanceof Map) {
			toVal = new LinkedList<Map<String, Object>>();
			for (Object obj : (List) val) {
				Map<String, Object> m = new LinkedHashMap<String, Object>();
				for (Object kk : ((Map) obj).keySet()) {
					String k = mapValueToKey(kk, msgMapping);
					if (k != null) {
						log.debug("unpackArgs key " + kk + " => " + k);
						m.put(k, ((Map) obj).get(kk));
					} else {
						m.put((String) kk, ((Map) obj).get(kk));
					}
				}
				((List) toVal).add(m);
			}
		} else if (val instanceof Map) {
			toVal = new LinkedHashMap<String, Object>();
			for (Object kk : ((Map) val).keySet()) {
				String k = mapValueToKey(kk, msgMapping);
				if (k != null) {
					log.debug("unpackArgs key " + kk + " => " + k);
					((Map) toVal).put(k, ((Map) val).get(kk));
				} else {
					((Map) toVal).put((String) kk, ((Map) val).get(kk));
				}
			}
		}
		return toVal;
	}

	private static String mapValueToKey(Object val, Map<String, String> msgMapping) {
		if (msgMapping.containsValue(val)) {
			for (Map.Entry<String, String> entry : msgMapping.entrySet()) {
				if (entry.getValue().equals(val)) {
					return entry.getKey();
				}
			}
		}
		return null;
	}
}
